Skip to content

offer: make the merkle tree signature public #3892

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

vincenzopalazzo
Copy link
Contributor

This is helpful for users who want to use the Merkle tree signature in their own code, for example, to verify the signature of bolt12 invoices or recreate it.

Very useful for people who are building command line tools for the bolt12 offers.

I am opening this, but I do not know if it is something that you want to do, but at the same time, IMHO, this is very useful to expose because it allows to use LDK in command line tools and in learning tools.

However, this is not strictly necessary because Bolt12Invoice::try_from already verifies the signature, but I would open this to know your point of view.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Jun 26, 2025

👋 Thanks for assigning @jkczyz as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@dunxen
Copy link
Contributor

dunxen commented Jun 26, 2025

I'm not personally opposed to this and it may be nice to expose it for the use cases you mention.
However, maybe the doc comments for these should be updated to indicate that these don't need to be used directly and they're really for internal use, but made public for use in applications like you specified. We should redirect them to the appropriate methods for production use.

I'm also not sure about any API guarantees for these methods. I don't think it's too much of a hassle to treat them the same way we treat any other public API.

Copy link

codecov bot commented Jun 26, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 88.99%. Comparing base (1d507f2) to head (f5fbd6c).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3892      +/-   ##
==========================================
- Coverage   89.00%   88.99%   -0.01%     
==========================================
  Files         166      166              
  Lines      119990   120093     +103     
  Branches   119990   120093     +103     
==========================================
+ Hits       106798   106878      +80     
- Misses      10770    10788      +18     
- Partials     2422     2427       +5     
Flag Coverage Δ
fuzz 22.48% <0.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot
Copy link

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @TheBlueMatt @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @TheBlueMatt @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@vincenzopalazzo
Copy link
Contributor Author

cc @jkczyz

@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch 2 times, most recently from dd046d5 to 29e5bd6 Compare July 8, 2025 20:46
@TheBlueMatt TheBlueMatt removed their request for review July 9, 2025 01:06
@TheBlueMatt
Copy link
Collaborator

Will take a look at this after @jkczyz's comments are addressed, assuming he thinks it needs a second reviewer at that point.

@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch from 0c7fe08 to 123e16f Compare July 9, 2025 10:19
@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch 2 times, most recently from 7429b6a to 1b958e9 Compare July 9, 2025 11:13
@vincenzopalazzo
Copy link
Contributor Author

vincenzopalazzo commented Jul 9, 2025

Ok, I pushed the change that @jkczyz requested, and now the PR is organised in two commits as follows:

  • bed3812 where expose the sign and verify method to verify manually an invoice signature, this is nice for people that are building command line tools and want to show how the signature verification is described inside the spec
    -e60b87c38c043114642eda5499864c3c6898aa64 expose the offer_id inside the Bolt12Invoice that allows to compare it with the Offer to make sure that the invoice is linked with the Bolt12 one. This is useful for a sanity check, but requires trusting that the decoding of ldk will always verify the signature of the invoice

Let me know if you are fine with the commit divided in this way, or you prefer e60b87c

@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch from 1b958e9 to 699f37f Compare July 9, 2025 11:23
@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch from 22ef851 to e60b87c Compare July 9, 2025 20:12
@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch 2 times, most recently from ec9b1c6 to 98d51cd Compare July 10, 2025 07:19
@vincenzopalazzo vincenzopalazzo requested a review from jkczyz July 10, 2025 11:06
@jkczyz
Copy link
Contributor

jkczyz commented Jul 10, 2025

BTW, could you make sure the commit messages are wrapped at 72 characters?

@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch from 98d51cd to bf5ca05 Compare July 10, 2025 15:48
@vincenzopalazzo
Copy link
Contributor Author

Done thanks, should be ready for another pass!

@vincenzopalazzo vincenzopalazzo requested a review from jkczyz July 10, 2025 15:49
@valentinewallace valentinewallace removed their request for review July 10, 2025 15:50
@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch from bf5ca05 to a68da7e Compare July 10, 2025 20:28
Comment on lines +979 to +984
/// Returns the [`OfferId`] if this invoice corresponds to an [`Offer`].
///
/// [`Offer`]: crate::offers::offer::Offer
pub fn offer_id(&self) -> Option<OfferId> {
self.offer_id
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry... we should put this in invoice_accessors_common such that UnsignedBolt12Invoice, UnsignedStaticInvoice, and StaticInvoice also get this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I was worried about this accessor in another invoice. Because this change is a little bit disruptive, I made a fixup commit 2da0ac8 to have a first iteration of review on the changes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... come to think of it we probably don't need this for the unsigned invoices. Sorry about the hassle. Instead, we can drop the fixup and simply implement it directly for StaticInvoice. That should never come from a Refund, so its offer_id method won't need to use Option. Thus, we shouldn't use the macro.

@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch from 2da0ac8 to a95815c Compare July 11, 2025 08:38
Comment on lines 662 to 666
let offer_id = match &contents {
InvoiceContents::ForOffer { .. } => Some(OfferId::from_valid_bolt12_tlv_stream(&bytes)),
InvoiceContents::ForRefund { .. } => None,
};
Self { bytes, experimental_bytes, contents, tagged_hash, offer_id }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkczyz do you think that we should add in OfferId a maybe_from_valid_bolt12_tlv_stream(&bytes) -> Option<OfferId> to include the refund case without duplicate the match here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd need to take in the InvoiceContents then, too. Leaning towards not doing this for unsigned invoices after all. See next comment.

Comment on lines 662 to 666
let offer_id = match &contents {
InvoiceContents::ForOffer { .. } => Some(OfferId::from_valid_bolt12_tlv_stream(&bytes)),
InvoiceContents::ForRefund { .. } => None,
};
Self { bytes, experimental_bytes, contents, tagged_hash, offer_id }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd need to take in the InvoiceContents then, too. Leaning towards not doing this for unsigned invoices after all. See next comment.

Comment on lines +979 to +984
/// Returns the [`OfferId`] if this invoice corresponds to an [`Offer`].
///
/// [`Offer`]: crate::offers::offer::Offer
pub fn offer_id(&self) -> Option<OfferId> {
self.offer_id
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... come to think of it we probably don't need this for the unsigned invoices. Sorry about the hassle. Instead, we can drop the fixup and simply implement it directly for StaticInvoice. That should never come from a Refund, so its offer_id method won't need to use Option. Thus, we shouldn't use the macro.

@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch 3 times, most recently from 079db78 to 0e5217d Compare July 15, 2025 08:38
@vincenzopalazzo
Copy link
Contributor Author

Ok pushed the changes, not sure that I have got 100% this review, but let me know if there is something that is still pending #3892 (comment)

@vincenzopalazzo vincenzopalazzo requested a review from jkczyz July 15, 2025 08:39
@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch 2 times, most recently from 097c4a7 to 8df655b Compare July 15, 2025 08:56
@vincenzopalazzo
Copy link
Contributor Author

I noted that for some reason, I was not making public the tagged_hash inside the Bolt12Invoice which was the way to execute manual verification. I pushed the code in ea5685c and added a test to show a user how to use the API that we are now making public

@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch from 8df655b to 5ceaf4d Compare July 15, 2025 09:05
let issuer_sign_pubkey = offer.issuer_signing_pubkey().unwrap();
let tagged_hash = invoice.tagged_hash();
let signature = invoice.signature();
assert!(merkle::verify_signature(&signature, tagged_hash, issuer_sign_pubkey).is_ok());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like exposing verify_signature doesn't provide much value as it's simply a wrapper around the secp256k1 crate. Maybe it is fine to expose as a convenience function, but it isn't strictly necessary to support the use case (i.e., all we need to do is make tagged_hash accessible).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see it, from an API point of view I think it is nice to have a verify_signature, but I am not oppose to use secp256k1

@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch from 5ceaf4d to dde9e32 Compare July 16, 2025 09:53
This is helpfull for the users that want to use the merkle tree
signature in their own code, for example to verify the
signature of bolt12 invoices or recreate it.

Very useful for people that are building command line tools
for the bolt12 offers.

Signed-off-by: Vincenzo Palazzo <[email protected]>
@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch from dde9e32 to c5fe6a3 Compare July 16, 2025 09:53
- Add an Option<OfferId> field to Bolt12Invoice to
  track the originating offer.
- Compute the offer_id for invoices created from
  offers by extracting the offer TLV records and
  hashing them with the correct tag.
- Expose a public offer_id() accessor on invoice.
- Add tests to ensure the offer_id in the invoice
  matches the originating Offer, and that refund
  invoices have no offer_id.
- All existing and new tests pass. This enables
  linking invoices to their originating offers
  in a robust and spec-compliant way.

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Signed-off-by: Vincenzo Palazzo <[email protected]>
@vincenzopalazzo vincenzopalazzo force-pushed the macros/markle-signature branch from c5fe6a3 to f5fbd6c Compare July 16, 2025 09:55
@vincenzopalazzo vincenzopalazzo requested a review from jkczyz July 16, 2025 09:56
@vincenzopalazzo
Copy link
Contributor Author

vincenzopalazzo commented Jul 16, 2025

I had to rebase to make the CI happy because there was a problem fixed by some PR that is now merged.

So I think the only missing thing to resolve is the following one #3892 (comment)

Would love to have the API to hide the secp256k1, but I also understand that the code is simple enough that we do not need an API for skilled users. Let me know what you preferer

}

#[test]
fn verifies_invoice_signature_with_tagged_hash() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this third test belongs in the first commit. :P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants